package Participants.CapitoGuye;

import java.util.ArrayList;
import java.util.List;

import Othello.Move;
import java.text.NumberFormat;

class World {

    /*------------------------------------------------------------------*\
    |*							Constructeurs							*|
    \*------------------------------------------------------------------*/
    public World(int size) {
        etatPartie = EtatPartie.ouverture;
        this.size = size;
        this.nbCoupsJoues = 0;

        plateau = new Case[size][size];
        listPerimetreJouable = new ArrayList<Case>();

        initWorld();
    }

    public World(World world) {
        this.etatPartie = world.getEtatPartie();
        this.size = world.getSize();
        this.nbCoupsJoues = world.getNbCoupsJoues();
        this.nombrePasse = world.nombrePasse;

        this.DEF_CASE_HasChanged = world.DEF_CASE_HasChanged;
        this.FORCE_CASE_HasChanged = world.DEF_CASE_HasChanged;

        this.plateau = new Case[world.plateau.length][world.plateau.length];
        for (int i = 0; i < world.plateau.length; i++) {
            for (int j = 0; j < world.plateau[i].length; j++) {
                this.plateau[i][j] = new Case(world.plateau[i][j]);
            }
        }

        for (int i = 0; i < this.nbPionsJoueur.length; i++) {
            this.nbPionsJoueur[i] = world.nbPionsJoueur[i];
        }
        this.listPerimetreJouable = new ArrayList<Case>();
        for (Case perimetre : world.getListPerimetreJouable()) {
            this.listPerimetreJouable.add(this.plateau[perimetre.getI()][perimetre.getJ()]);
        }

        if (world.FORCE_CASE_HasChanged) {
            for (int i = 0; i < world.FORCE_CASE.length; i++) {
                for (int j = 0; j < world.FORCE_CASE[i].length; j++) {
                    this.FORCE_CASE[i][j] = world.FORCE_CASE[i][j];
                }
            }
        }

        if (world.DEF_CASE_HasChanged) {
            for (int i = 0; i < world.DEF_CASE.length; i++) {
                for (int j = 0; j < world.DEF_CASE[i].length; j++) {
                    this.DEF_CASE[i][j] = world.DEF_CASE[i][j];
                }
            }
        }
    }

    /*------------------------------------------------------------------*\
    |*							Methodes Private						*|
    \*------------------------------------------------------------------*/
    private void initWorld() {
        // Initialisation à -1 pour case vide
        for (int i = 0; i < plateau.length; i++) {
            for (int j = 0; j < plateau[i].length; j++) {
                plateau[i][j] = new Case(i, j, -1);
            }
        }

        // 4 pions initiaux
        plateau[3][3].set(1);
        updateListPerimetreJouable(plateau[3][3]);
        plateau[4][3].set(0);
        updateListPerimetreJouable(plateau[4][3]);
        plateau[3][4].set(0);
        updateListPerimetreJouable(plateau[3][4]);
        plateau[4][4].set(1);
        updateListPerimetreJouable(plateau[4][4]);
    }

    /**
     * C'est cette methode qui teste iterativement si on va pouvoir ou non "mange" des pions adversese.
     */
    private List<Case> parcoursDirection(Case caseCentral, Vecteur2D vecteur2d) {
        List<Case> casesAdverses = new ArrayList<Case>();
        Case nextCase = getCaseVecteur(caseCentral, vecteur2d);

        while (nextCase != null && nextCase.getVal() == Joueur.inverseJoueur()) {
            casesAdverses.add(nextCase);
            nextCase = getCaseVecteur(nextCase, vecteur2d);
        }

        if (nextCase != null && nextCase.getVal() == Joueur.JOUEUR_ACTUEL) {
            return casesAdverses;
        } else {
            return null;
        }
    }

    private Case getCaseVecteur(Case caseCentral, Vecteur2D vecteur2d) {
        try {
            return plateau[caseCentral.getI() + vecteur2d.getX()][caseCentral.getJ() + vecteur2d.getY()];
        } catch (Exception e) {
            return null;
        }
    }

    private List<Case> getPerimetreCase(Case caseCentral) {
        List<Case> perimetre = new ArrayList<Case>();

        for (int i = caseCentral.getI() - 1; i <= caseCentral.getI() + 1; i++) {
            for (int j = caseCentral.getJ() - 1; j <= caseCentral.getJ() + 1; j++) {
                if (i != caseCentral.getI() || j != caseCentral.getJ()) {
                    Case caseVecteur = getCaseVecteur(caseCentral, new Vecteur2D(i - caseCentral.getI(), j - caseCentral.getJ()));
                    if (caseVecteur != null) {
                        perimetre.add(caseVecteur);
                    }
                }
            }
        }
        return perimetre;
    }

    private List<Case> getCroixCase(Case caseCentral) {
        List<Case> x = new ArrayList<Case>();

        Case caseVecteur = null;
        for (int i = 0; i < croix.length; i++) {
            caseVecteur = getCaseVecteur(caseCentral, croix[i]);
            if (caseVecteur != null) {
                x.add(caseVecteur);
            }
        }

        return x;
    }

    private List<Case> getDiagCase(Case caseCentral) {
        List<Case> d = new ArrayList<Case>();

        Case caseVecteur = null;
        for (int i = 0; i < diag.length; i++) {
            caseVecteur = getCaseVecteur(caseCentral, diag[i]);
            if (caseVecteur != null) {
                d.add(caseVecteur);
            }
        }

        return d;
    }

    /*------------------------------------------------------------------*\
    |*							Methodes Public							*|
    \*------------------------------------------------------------------*/
    // TODO: Supprimer. Car methode de prosepection pour le Main (test de l'alphabeta)
    public void poserJeton(Move move) {
        plateau[move.i][move.j].set(Joueur.JOUEUR_ACTUEL);
        updateListPerimetreJouable(plateau[move.i][move.j]);
    }

    boolean isDefCase(Case defcase) {
        if (defcase.isInAnEdge()) {
            for (Case def : getCroixCase(defcase)) {
                if (DEF_CASE[def.getI()][def.getJ()] == defcase.getVal()) {
                    return true;
                }
            }
        } else {
            int l = defcase.getI();
            int k = defcase.getJ();
            int table[] = tablePatternCase[k - 1][l - 1];
            int x, y;
            boolean def;
            for (int i = 0; i < table.length; i++) {
                def = true;

                for (int j = 0; j < patternCaseDef[table[i]].length; j++) {
                    x = patternCaseDef[table[i]][j].getX();
                    y = patternCaseDef[table[i]][j].getY();
                    int val = DEF_CASE[l + x][k + y];
                    if (val != defcase.getVal()) {
                        def = false;
                        break;
                    }
                }
                if (def) {
                    return true;
                }
            }
        }
        return false;
    }

    List<Case> isLigneFull() {
        boolean b1 = true, b2 = true, b3 = true, b4 = true;
        List<Case> c1 = new ArrayList<Case>(),
                c2 = new ArrayList<Case>(),
                c3 = new ArrayList<Case>(),
                c4 = new ArrayList<Case>();

        for (int k = 0; k < 8; k++) {
            if (b1 && plateau[0][k].getVal() != -1 && DEF_CASE[0][k] == 8) {
                c1.add(plateau[0][k]);
            } else if (b1 && plateau[0][k].getVal() == -1) {
                b1 = false;
                c1.clear();
            }
            if (b2 && plateau[k][7].getVal() != -1 && DEF_CASE[k][7] == 8) {
                c2.add(plateau[k][7]);
            } else if (b2 && plateau[k][7].getVal() == -1) {
                b2 = false;
                c2.clear();
            }
            if (b3 && plateau[7][k].getVal() != -1 && DEF_CASE[7][k] == 8) {
                c3.add(plateau[7][k]);
            } else if (b3 && plateau[7][k].getVal() == -1) {
                b3 = false;
                c3.clear();
            }
            if (b4 && plateau[k][0].getVal() != -1 && DEF_CASE[0][0] == 8) {
                c4.add(plateau[k][0]);
            } else if (b4 && plateau[k][0].getVal() == -1) {
                b4 = false;
                c4.clear();
            }

            if (!(b1 || b2 || b3 || b4)) {
                return c1;
            }
        }
        c1.addAll(c2);
        c1.addAll(c3);
        c1.addAll(c4);
        return c1;
    }

    /**
     * Mise a jour du monde, a appele apres qu'un coup ait ete joues
     */
    public int updateWorld(Move move) {
        // Nouveau jeton pose
        plateau[move.i][move.j].set(Joueur.JOUEUR_ACTUEL);
        nbCoupsJoues++;

        if (plateau[move.i][move.j].isInCorner()) {
            FORCE_CASE_HasChanged = true;
            DEF_CASE_HasChanged = true;
            DEF_CASE[move.i][move.j] = Joueur.JOUEUR_ACTUEL;
            FORCE_CASE[move.i][(move.j == 0 ? 1 : 6)] = 350;
            FORCE_CASE[(move.i == 0 ? 1 : 6)][(move.j == 0 ? 1 : 6)] = 250;
            FORCE_CASE[(move.i == 0 ? 1 : 6)][move.j] = 350;
        }

        updateListPerimetreJouable(plateau[move.i][move.j]);
        List<Case> listCasesInverser = getListCaseInverser(plateau[move.i][move.j]);
        nbPionsJoueur[Joueur.JOUEUR_ACTUEL] += listCasesInverser.size() + 1;
        nbPionsJoueur[Joueur.inverseJoueur()] -= listCasesInverser.size();

        for (Case inverser : listCasesInverser) {
            inverser.set(Joueur.JOUEUR_ACTUEL);
        }


        //Bords
        if (DEF_CASE_HasChanged) {
            List<Case> bord = isLigneFull();
            for (Case b : bord) {
                DEF_CASE[b.getI()][b.getJ()] = b.getVal();
            }
            boolean idempotence = true;


            for (int i = 0; i < 8; i++) {
                while (idempotence) {
                    idempotence = false;
                    for (int j = 0; j < 8; j++) {
                        if (plateau[i][j].getVal() != -1 && DEF_CASE[i][j] == 8 && isDefCase(plateau[i][j])) {
                            DEF_CASE[i][j] = plateau[i][j].getVal();
                            idempotence = true;
                        }
                    }
                }
                idempotence = true;
            }
        }

        return listCasesInverser.size();
    }

    /**
     * Retourne la liste des cases a inverser en fonction d'une case central
     */
    public List<Case> getListCaseInverser(Case caseCentral) {
        List<Case> caseAInverser = new ArrayList<Case>();

        for (int i = 0; i <= croix.length; i++) {
            try {
                caseAInverser.addAll(parcoursDirection(caseCentral, croix[i]));
            } catch (Exception e) {
                // Pas de case a inverser
            }
        }
        for (int i = 0; i <= diag.length; i++) {
            try {
                caseAInverser.addAll(parcoursDirection(caseCentral, diag[i]));
            } catch (Exception e) {
                // Pas de case a inverser
            }
        }

        return caseAInverser;
    }

    public void updateListPerimetreJouable(Case caseJoue) {
        listPerimetreJouable.remove(caseJoue);

        for (Case casePerimetre : getPerimetreCase(caseJoue)) {
            if (casePerimetre.getVal() == -1 && !listPerimetreJouable.contains(casePerimetre)) {
                listPerimetreJouable.add(casePerimetre);
            }
        }
    }

    public void updateEtatPartie() {
        if (nbCoupsJoues < 6) {
            this.etatPartie = EtatPartie.ouverture;
        } else if (nbCoupsJoues < 14) {
            this.etatPartie = EtatPartie.debut;
        } else if (nbCoupsJoues < 60 - Joueur.DEPTH) {
            this.etatPartie = EtatPartie.millieu;
        } else {
            this.etatPartie = EtatPartie.fermeture;
        }
    }

    public int[] forceOccupation() {
        int force[] = {0, 0};
        for (int i = 0; i < FORCE_CASE.length; i++) {
            for (int j = 0; j < FORCE_CASE[i].length; j++) {
                if (plateau[i][j].getVal() == Joueur.JOUEUR_ACTUEL) {
                    force[Joueur.JOUEUR_ACTUEL] += FORCE_CASE[i][j];
                } else if (plateau[i][j].getVal() == Joueur.inverseJoueur()) {
                    force[Joueur.inverseJoueur()] += FORCE_CASE[i][j];
                }
            }
        }
        return force;
    }

    public int[] forceMobilite() {
        int mobilite[] = {0, 0};
        mobilite[Joueur.JOUEUR_ACTUEL] = getCoupsJouable().size();
        Joueur.JOUEUR_ACTUEL = Joueur.inverseJoueur();
        mobilite[Joueur.inverseJoueur()] = getCoupsJouable().size();
        Joueur.JOUEUR_ACTUEL = Joueur.inverseJoueur();
        return mobilite;
    }

    public int[] forceDef() {
        int def[] = {0, 0};
        for (int i = 0; i < DEF_CASE.length; i++) {
            for (int j = 0; j < DEF_CASE[i].length; j++) {
                if (plateau[i][j].getVal() == Joueur.JOUEUR_ACTUEL) {
                    def[Joueur.JOUEUR_ACTUEL] += DEF_CASE[i][j] != 8 ? 1 : 0;
                } else if (plateau[i][j].getVal() == Joueur.inverseJoueur()) {
                    def[Joueur.inverseJoueur()] += DEF_CASE[i][j] != 8 ? 1 : 0;
                }
            }
        }
        return def;
    }

    public int critereNbPions() {
        return nbPionsJoueur[Joueur.JOUEUR_ACTUEL] - nbPionsJoueur[Joueur.inverseJoueur()];
    }

    public int getNbCaseVide(Case caseCentrale) {
        List<Case> listPerimetre = getPerimetreCase(caseCentrale);
        int nbCaseVide = 0;
        for (Case casePermitre : listPerimetre) {
            if (casePermitre.getVal() == -1) {
                nbCaseVide++;
            }
        }
        return nbCaseVide;
    }

    public boolean hasCaseVide(Case caseCentrale) {
        List<Case> listPerimetre = getPerimetreCase(caseCentrale);
        for (Case casePermitre : listPerimetre) {
            if (casePermitre.getVal() == -1) {
                return true;
            }
        }
        return false;
    }
    /*------------------------------*\
    |*				Get				*|
    \*------------------------------*/

    public Case[][] getPlateau() {
        return this.plateau;
    }

    public int getSize() {
        return this.size;
    }

    public EtatPartie getEtatPartie() {
        return this.etatPartie;
    }

    public List<Case> getListPerimetreJouable() {
        return this.listPerimetreJouable;
    }

    public List<Case> getCoupsJouable() {
        List<Case> coupsJouable = new ArrayList<Case>();

        for (Case caseJouable : listPerimetreJouable) {
            if (!getListCaseInverser(caseJouable).isEmpty()) {
                coupsJouable.add(caseJouable);
            }
        }

        return coupsJouable;
    }

    public int getNbPionsJoueurActuel() {
        return nbPionsJoueur[Joueur.JOUEUR_ACTUEL];
    }

    public int getNbPionsJoueurAdversaire() {
        return nbPionsJoueur[Joueur.inverseJoueur()];
    }

    public int getNbPionsJoueur(int joueur) {
        return nbPionsJoueur[joueur];
    }

    public int getNbCoupsJoues() {
        return this.nbCoupsJoues;
    }

    /*------------------------------*\
    |*			 Object				*|
    \*------------------------------*/
    @Override
    public String toString() {

        NumberFormat nf4 = NumberFormat.getInstance();
        nf4.setMinimumIntegerDigits(4);
        NumberFormat nf3 = NumberFormat.getInstance();
        nf3.setMinimumIntegerDigits(3);
        StringBuilder str = new StringBuilder();
        str.append("    0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 -");

        if (FORCE_CASE_HasChanged) {
            str.append(" ______  0   -   1   -   2   -   3   -   4   -   5   -   6   -   7   -");
        }
        if (DEF_CASE_HasChanged) {
            str.append(" ______0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 -");
        }
        str.append("\n--------------------------------------------------------------------------------------------------------------------------------------\n");
        for (int j = 0; j < plateau.length; j++) {
            str.append(j + " | ");
            for (int i = 0; i < plateau[j].length; i++) {
                if (listPerimetreJouable.contains(plateau[i][j])) {
                    str.append("#");
                } else if (plateau[i][j].getVal() == -1) // Pour une representation du monde aligne (en console)
                {
                    str.append("8");
                } else {
                    str.append(plateau[i][j].getVal());
                }
                str.append(" - ");
            }

            if (FORCE_CASE_HasChanged) {
                str.append("______");
                for (int i = 0; i < FORCE_CASE[j].length; i++) {
                    if (FORCE_CASE[i][j] < 0) {
                        str.append(" " + nf3.format(FORCE_CASE[i][j]));
                    } else {
                        str.append(nf4.format(FORCE_CASE[i][j]));
                    }
                    str.append(" - ");
                }
            }
            if (DEF_CASE_HasChanged) {
                str.append("______");
                for (int i = 0; i < DEF_CASE[j].length; i++) {
                    str.append(DEF_CASE[i][j]);
                    str.append(" - ");
                }
            }
            str.append("\n");
        }
        return str.toString();
    }

    public World cloneOf() {
        return new World(this);
    }

    /*------------------------------------------------------------------*\
    |*							Attributs Private						*|
    \*------------------------------------------------------------------*/
    private EtatPartie etatPartie;
    private Case plateau[][];
    private int size;
    private List<Case> listPerimetreJouable;
    private int nbCoupsJoues;
    private int nbPionsJoueur[] = {2, 2};
    public int nombrePasse = 0;
    /*------------------------------*\
    |*			  Static			*|
    \*------------------------------*/
    public boolean FORCE_CASE_HasChanged = false;
    public int FORCE_CASE[][] = {
        {500, -150, 50, 30, 30, 50, -150, 500},
        {-150, -250, 0, 0, 0, 0, -250, -150},
        {50, 0, 3, 10, 10, 3, 0, 50},
        {30, 0, 10, 25, 25, 10, 0, 30},
        {30, 0, 10, 25, 25, 10, 0, 30},
        {50, 0, 3, 10, 10, 3, 0, 50},
        {-150, -250, 0, 0, 0, 0, -250, -150},
        {500, -150, 50, 30, 30, 50, -150, 500}};
    public boolean DEF_CASE_HasChanged = false;
    public int DEF_CASE[][] = {
        {8, 8, 8, 8, 8, 8, 8, 8},
        {8, 8, 8, 8, 8, 8, 8, 8},
        {8, 8, 8, 8, 8, 8, 8, 8},
        {8, 8, 8, 8, 8, 8, 8, 8},
        {8, 8, 8, 8, 8, 8, 8, 8},
        {8, 8, 8, 8, 8, 8, 8, 8},
        {8, 8, 8, 8, 8, 8, 8, 8},
        {8, 8, 8, 8, 8, 8, 8, 8}};
    public static final Vecteur2D croix[] = {new Vecteur2D(0, 1),
        new Vecteur2D(1, 0),
        new Vecteur2D(0, -1),
        new Vecteur2D(-1, 0)
    };
    public static final Vecteur2D diag[] = {new Vecteur2D(-1, -1),
        new Vecteur2D(-1, 1),
        new Vecteur2D(1, -1),
        new Vecteur2D(1, 1)
    };
    //Pattern à verifier pour savoir si notre case qui n'est pas dans les bords est définitives (pattern pour pion de meme couleur qui sont deja def)
    public static Vecteur2D patternCaseDef[][] = {
        //Bord haut
        {new Vecteur2D(-1, 0), new Vecteur2D(-1, -1), new Vecteur2D(0, -1), new Vecteur2D(1, -1)},
        {new Vecteur2D(1, 0), new Vecteur2D(-1, -1), new Vecteur2D(0, -1), new Vecteur2D(1, -1)},
        //Bord droit
        {new Vecteur2D(0, -1), new Vecteur2D(1, -1), new Vecteur2D(1, 0), new Vecteur2D(1, 1)},
        {new Vecteur2D(0, 1), new Vecteur2D(1, -1), new Vecteur2D(1, 0), new Vecteur2D(1, 1)},
        //Bord bas
        {new Vecteur2D(1, 0), new Vecteur2D(1, 1), new Vecteur2D(0, 1), new Vecteur2D(-1, 1)},
        {new Vecteur2D(-1, 0), new Vecteur2D(1, 1), new Vecteur2D(0, 1), new Vecteur2D(-1, 1)},
        //Bord gauche
        {new Vecteur2D(0, 1), new Vecteur2D(-1, 1), new Vecteur2D(-1, 0), new Vecteur2D(-1, -1)},
        {new Vecteur2D(0, -1), new Vecteur2D(-1, 1), new Vecteur2D(-1, 0), new Vecteur2D(-1, -1)}
    };
    public static int tablePatternCase[][][] = {
        {{0, 7, 1, 6}, {0, 1}, {0, 1}, {1, 0}, {1, 0}, {1, 2, 0, 3}},
        {{7, 6}, {0, 7, 1, 6}, {0, 1}, {1, 0}, {1, 2, 0, 3}, {2, 3}},
        {{7, 6}, {7, 6}, {0, 7, 1, 6}, {1, 2, 0, 3}, {2, 3}, {2, 3}},
        {{6, 7}, {6, 7}, {6, 5, 7, 4}, {4, 3, 5, 2}, {3, 2}, {3, 2}},
        {{6, 7}, {6, 5, 7, 4}, {5, 4}, {4, 5}, {4, 3, 5, 2}, {3, 2}},
        {{6, 5, 7, 4}, {6, 7}, {5, 4}, {4, 5}, {4, 5}, {4, 3, 5, 2}}
    };
}
